﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Kawanon.Foundation;
using Microsoft.Win32;

namespace Kawanon.QtHoney
{
	public class ArchiveVersionMismatchException : ApplicationException
	{
	}

	// QTTabBar でロード時に BinaryFormatter がアセンブリを特定できない問題の対策（GACがらみ？）
	public class LocalSerializationBinder : SerializationBinder
	{
		public override Type BindToType(string assemblyName, string typeName)
		{
			//NOTE: アセンブリから直接タイプ名を取得する
			typeName = typeName.Replace("DefinedCommand", "BuiltInCommand");

			if (typeName.EndsWith("ExpressionArchiveHeader"))
			{
				typeName = typeof(Persistence.ArchiveHeader).FullName;
			}
			if (typeName.EndsWith("AddressBars+Target") || typeName.EndsWith("OpenFolder+Target"))
			{
				typeName = typeof(NavigationTargets).FullName;
			}

			Type ty = null;

			//NOTE: このアセンブリ内の型を検索
			ty = Assembly.GetExecutingAssembly().GetType(typeName);
			if (ty != null)
			{
				return ty;
			}

			//NOTE: アセンブリ内になければ、共通クラスの型のはず
			ty = Type.GetType(Assembly.CreateQualifiedName(assemblyName, typeName));
			if (ty != null)
			{
				return ty;
			}

			//DebugHelpers.Echo(ty +"_ "+ typeName +"_ "+ assemblyName);

			//StringBuilder buf = new StringBuilder();
			//foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
			//{
			//    buf.Append(asm.FullName);
			//    buf.Append("\r\n");
			//}
			//DebugHelpers.Echo(buf);
			//if (ty == null)
			//{
			//    ////TODO: 応急処置
			//    //if (typeName == "System.Collections.Generic.List`1[[Kawanon.Usability.WheelExpression, QTHoney, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]")
			//    //{
			//    //    Type t = Type.GetType(String.Format("{0}, {1}", typeName, assemblyName));
			//    //    Dbg.Echo(t);
			//    //    ty = typeof(List<WheelExpression>);
			//    //}
			//}
			return null;
		}
	}

	public static class Persistence
	{
		private const int ARCHIVE_VERSION_MISC = 1;

		[Serializable]
		public struct ArchiveHeader
		{
			public int Version { get; set; }
			public int ObjectCount { get; set; }
		}

		public static void ArchiveFromArray(Stream stream, object[] list)
		{
			DeflateStream deflate = new DeflateStream(stream, CompressionMode.Compress, true);

			BinaryFormatter bin = new BinaryFormatter();
			Array.ForEach(list, obj => bin.Serialize(deflate, obj));

			deflate.Flush();	// フラッシュして閉じないと読み込みで失敗する
			deflate.Close();	//（エンドマークが書き込まれない）
		}

		public static void SaveBinary(string val, object[] list)
		{
			MemoryStream ms = new MemoryStream();
			ArchiveFromArray(ms, list);

			RegistryKey key = Plugins.OpenRegistryKey(true);
			key.SetValue(val, ms.ToArray(), RegistryValueKind.Binary);
			key.Flush();
			key.Close();
		}

		public static Stream CreateValueStream(string name)
		{
			RegistryKey key = Plugins.OpenRegistryKey(false);
			if (key == null)
			{
				return null;
			}

			byte[] bytes = (byte[])key.GetValue(name);
			key.Close();
			if (bytes == null)
			{
				return null;
			}
			MemoryStream ms = new MemoryStream(bytes);
			return ms;
		}

		public delegate object Loader();

		public static Loader CreateLoader(Stream istream)
		{
			DeflateStream inflate = new DeflateStream(istream, CompressionMode.Decompress);
			BinaryFormatter bin = new BinaryFormatter();
			bin.Binder = new LocalSerializationBinder();

			return delegate()
			{
				return bin.Deserialize(inflate);
			};
		}

		private static string GetArchiveName(Type t)
		{
			return t.Name.Replace("Information", "");
		}

		public static object GetInformation(Type t)
		{
			object info = null;
			try
			{
				info = LoadInformation(t);
			}
			catch (SerializationException)
			{
				// 読み込み失敗時は新しいオブジェクトを返す
			}
			catch (ArchiveVersionMismatchException)
			{
			}
			if (info == null)
			{
				return Activator.CreateInstance(t);
			}
			return info;
		}

		public static object LoadInformation(Type t)
		{
			Stream stream = CreateValueStream(GetArchiveName(t));
			if (stream == null)
			{
				return null;
			}
			return LoadInformation(stream);
		}

		public static T GetInformation<T>()
			where T : class
		{
			return (T)GetInformation(typeof(T));
		}

		public static T LoadInformation<T>()
			where T : class
		{
			return (T)LoadInformation(typeof(T));
		}

		public static object LoadInformation(Stream istream)
		{
			Loader loader = CreateLoader(istream);

			int ver = (int)loader();
			if (ver != ARCHIVE_VERSION_MISC)
			{
				throw new ArchiveVersionMismatchException();
			}

			return loader();
		}

		public static void SaveInformation(object info, bool notify)
		{
			Type t = info.GetType();
			string name = GetArchiveName(t);

			SaveBinary(name, new object[] { ARCHIVE_VERSION_MISC, info });

			if (notify)
			{
				Plugins.NotifySettingsChanged();
			}
		}

		public static void SaveInformation(object info)
		{
			SaveInformation(info, true);
		}

		private static IEnumerable<T> UnpackArchiveInternal<T>(Stream istream, long pos, bool throwOnFail)
		{
			// IEnumerable の先頭要素に戻ったときの対策
			istream.Seek(pos, SeekOrigin.Begin);

			Persistence.Loader loader = Persistence.CreateLoader(istream);
			ArchiveHeader header = (ArchiveHeader)loader();
			if (header.Version != ARCHIVE_VERSION_MISC)
			{
				if (throwOnFail)
				{
					throw new ArchiveVersionMismatchException();
				}
				else
				{
					yield break;
				}
			}
			for (int i = 0; i < header.ObjectCount; i++)
			{
				T obj;
				try
				{
					obj = (T)loader();
				}
				catch (SerializationException ex)
				{
					if (throwOnFail)
					{
						throw ex;
					}
					else
					{
						continue;
					}
				}
				yield return obj;
			}
		}

		public static IEnumerable<T> UnpackArchive<T>(Stream istream, bool throwOnFail)
		{
			long pos = istream.Position;
			return UnpackArchiveInternal<T>(istream, pos, throwOnFail);
		}

		public static IEnumerable<T> UnpackArchive<T>(Stream istream)
		{
			return UnpackArchive<T>(istream, true);
		}

		public static object[] CreateArchive<T>(IEnumerable<T> e)
		{
			List<object> list = new List<object>();
			ArchiveHeader header = new ArchiveHeader();
			header.Version = ARCHIVE_VERSION_MISC;
			header.ObjectCount = Utility.CountOf(e);

			list.Add(header);
			foreach (T obj in e)
			{
				list.Add(obj);
			}
			return list.ToArray();
		}
	}
}
